<template>
    <div class="waves"></div>
</template>

<script>
class ShaderProgram {
  constructor(holder, options = {}) {
    options = Object.assign(
        {
          antialias: false,
          depthTest: false,
          mousemove: false,
          autosize: true,
          side: "front",
          vertex: `
        precision highp float;
        attribute vec4 a_position;
        attribute vec4 a_color;
        uniform float u_time;
        uniform vec2 u_resolution;
        uniform vec2 u_mousemove;
        uniform mat4 u_projection;
        varying vec4 v_color;
        void main() {
          gl_Position = u_projection * a_position;
          gl_PointSize = (10.0 / gl_Position.w) * 100.0;
          v_color = a_color;
        }`,
          fragment: `
        precision highp float;
        uniform sampler2D u_texture;
        uniform int u_hasTexture;
        varying vec4 v_color;
        void main() {
          if ( u_hasTexture == 1 ) {
            gl_FragColor = v_color * texture2D(u_texture, gl_PointCoord);
          } else {
            gl_FragColor = v_color;
          }
        }`,
          uniforms: {},
          buffers: {},
          camera: {},
          texture: null,
          onUpdate: () => {},
          onResize: () => {}
        },
        options
    );
    const uniforms = Object.assign(
        {
          time: {
            type: "float",
            value: 0
          },
          hasTexture: {
            type: "int",
            value: 0
          },
          resolution: {
            type: "vec2",
            value: [0, 0]
          },
          mousemove: {
            type: "vec2",
            value: [0, 0]
          },
          projection: {
            type: "mat4",
            value: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
          }
        },
        options.uniforms
    );
    const buffers = Object.assign(
        {
          position: {
            size: 3,
            data: []
          },
          color: {
            size: 4,
            data: []
          }
        },
        options.buffers
    );
    const camera = Object.assign(
        {
          fov: 60,
          near: 1,
          far: 10000,
          aspect: 1,
          z: 100,
          perspective: true
        },
        options.camera
    );
    const canvas = document.createElement("canvas");
    const gl = canvas.getContext("webgl", {
      antialias: options.antialias
    });
    if (!gl) return false;
    this.count = 0;
    this.gl = gl;
    this.canvas = canvas;
    this.camera = camera;
    this.holder = holder;
    this.onUpdate = options.onUpdate;
    this.onResize = options.onResize;
    this.data = {};
    holder.appendChild(canvas);
    this.createProgram(options.vertex, options.fragment);
    this.createBuffers(buffers);
    this.createUniforms(uniforms);
    this.updateBuffers();
    this.updateUniforms();
    this.createTexture(options.texture);
    gl.enable(gl.BLEND);
    gl.enable(gl.CULL_FACE);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
    gl[options.depthTest ? "enable" : "disable"](gl.DEPTH_TEST);
    if (options.autosize)
      window.addEventListener("resize", e => this.resize(e), false);
    if (options.mousemove)
      window.addEventListener("mousemove", e => this.mousemove(e), false);
    this.resize();
    this.update = this.update.bind(this);
    this.time = {
      start: performance.now(),
      old: performance.now()
    };
    this.update();
  }
  mousemove(e) {
    let x = (e.pageX / this.width) * 2 - 1;
    let y = (e.pageY / this.height) * 2 - 1;
    this.uniforms.mousemove = [x, y];
  }
  // eslint-disable-next-line no-unused-vars
  resize(e) {
    const holder = this.holder;
    const canvas = this.canvas;
    const gl = this.gl;
    const width = (this.width = holder.offsetWidth);
    const height = (this.height = holder.offsetHeight);
    const aspect = (this.aspect = width / height);
    const dpi = (this.dpi = devicePixelRatio);
    canvas.width = width * dpi;
    canvas.height = height * dpi;
    canvas.style.width = 100 + "%";
    canvas.style.height = 100 + "%";
    gl.viewport(0, 0, width * dpi, height * dpi);
    gl.clearColor(0, 0, 0, 0);
    this.uniforms.resolution = [width, height];
    this.uniforms.projection = this.setProjection(aspect);
    this.onResize(width, height, dpi);
  }
  setProjection(aspect) {
    const camera = this.camera;
    if (camera.perspective) {
      camera.aspect = aspect;
      const fovRad = camera.fov * (Math.PI / 180);
      const f = Math.tan(Math.PI * 0.5 - 0.5 * fovRad);
      const rangeInv = 1.0 / (camera.near - camera.far);
      const matrix = [
        f / camera.aspect,
        0,
        0,
        0,
        0,
        f,
        0,
        0,
        0,
        0,
        (camera.near + camera.far) * rangeInv,
        -1,
        0,
        0,
        camera.near * camera.far * rangeInv * 2,
        0
      ];
      matrix[14] += camera.z;
      matrix[15] += camera.z;
      return matrix;
    } else {
      return [
        2 / this.width,
        0,
        0,
        0,
        0,
        -2 / this.height,
        0,
        0,
        0,
        0,
        1,
        0,
        -1,
        1,
        0,
        1
      ];
    }
  }
  createShader(type, source) {
    const gl = this.gl;
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      return shader;
    } else {
      console.log(gl.getShaderInfoLog(shader));
      gl.deleteShader(shader);
    }
  }
  createProgram(vertex, fragment) {
    const gl = this.gl;
    const vertexShader = this.createShader(gl.VERTEX_SHADER, vertex);
    const fragmentShader = this.createShader(gl.FRAGMENT_SHADER, fragment);
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
      gl.useProgram(program);
      this.program = program;
    } else {
      console.log(gl.getProgramInfoLog(program));
      gl.deleteProgram(program);
    }
  }
  createUniforms(data) {
    const gl = this.gl;
    const uniforms = (this.data.uniforms = data);
    const values = (this.uniforms = {});
    Object.keys(uniforms).forEach(name => {
      const uniform = uniforms[name];
      uniform.location = gl.getUniformLocation(this.program, "u_" + name);
      Object.defineProperty(values, name, {
        set: value => {
          uniforms[name].value = value;
          this.setUniform(name, value);
        },
        get: () => uniforms[name].value
      });
    });
  }
  setUniform(name, value) {
    const gl = this.gl;
    const uniform = this.data.uniforms[name];
    uniform.value = value;
    switch (uniform.type) {
      case "int": {
        gl.uniform1i(uniform.location, value);
        break;
      }
      case "float": {
        gl.uniform1f(uniform.location, value);
        break;
      }
      case "vec2": {
        gl.uniform2f(uniform.location, ...value);
        break;
      }
      case "vec3": {
        gl.uniform3f(uniform.location, ...value);
        break;
      }
      case "vec4": {
        gl.uniform4f(uniform.location, ...value);
        break;
      }
      case "mat2": {
        gl.uniformMatrix2fv(uniform.location, false, value);
        break;
      }
      case "mat3": {
        gl.uniformMatrix3fv(uniform.location, false, value);
        break;
      }
      case "mat4": {
        gl.uniformMatrix4fv(uniform.location, false, value);
        break;
      }
    }
    // ivec2       : uniform2i,
    // ivec3       : uniform3i,
    // ivec4       : uniform4i,
    // sampler2D   : uniform1i,
    // samplerCube : uniform1i,
    // bool        : uniform1i,
    // bvec2       : uniform2i,
    // bvec3       : uniform3i,
    // bvec4       : uniform4i,
  }
  updateUniforms() {
    // eslint-disable-next-line no-unused-vars
    const gl = this.gl;
    const uniforms = this.data.uniforms;
    Object.keys(uniforms).forEach(name => {
      const uniform = uniforms[name];
      this.uniforms[name] = uniform.value;
    });
  }
  createBuffers(data) {
    // eslint-disable-next-line no-unused-vars
    const gl = this.gl;
    const buffers = (this.data.buffers = data);
    const values = (this.buffers = {});
    Object.keys(buffers).forEach(name => {
      const buffer = buffers[name];
      buffer.buffer = this.createBuffer("a_" + name, buffer.size);
      Object.defineProperty(values, name, {
        set: data => {
          buffers[name].data = data;
          this.setBuffer(name, data);
          if (name == "position") this.count = buffers.position.data.length / 3;
        },
        get: () => buffers[name].data
      });
    });
  }
  createBuffer(name, size) {
    const gl = this.gl;
    const program = this.program;
    const index = gl.getAttribLocation(program, name);
    const buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.enableVertexAttribArray(index);
    gl.vertexAttribPointer(index, size, gl.FLOAT, false, 0, 0);
    return buffer;
  }
  setBuffer(name, data) {
    const gl = this.gl;
    const buffers = this.data.buffers;
    if (name == null && !gl.bindBuffer(gl.ARRAY_BUFFER, null)) return;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers[name].buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  }
  updateBuffers() {
    // eslint-disable-next-line no-unused-vars
    const gl = this.gl;
    const buffers = this.buffers;
    Object.keys(buffers).forEach(name => (buffers[name] = buffers.data));
    this.setBuffer(null);
  }
  createTexture(src) {
    const gl = this.gl;
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(
        gl.TEXTURE_2D,
        0,
        gl.RGBA,
        1,
        1,
        0,
        gl.RGBA,
        gl.UNSIGNED_BYTE,
        new Uint8Array([0, 0, 0, 0])
    );
    this.texture = texture;
    if (src) {
      this.uniforms.hasTexture = 1;
      this.loadTexture(src);
    }
  }
  loadTexture(src) {
    const gl = this.gl;
    const texture = this.texture;
    const textureImage = new Image();
    textureImage.onload = () => {
      gl.bindTexture(gl.TEXTURE_2D, texture);
      gl.texImage2D(
          gl.TEXTURE_2D,
          0,
          gl.RGBA,
          gl.RGBA,
          gl.UNSIGNED_BYTE,
          textureImage
      );
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      // gl.generateMipmap( gl.TEXTURE_2D )
    };
    textureImage.src = src;
  }
  update() {
    const gl = this.gl;
    const now = performance.now();
    const elapsed = (now - this.time.start) / 5000;
    const delta = now - this.time.old;
    this.time.old = now;
    this.uniforms.time = elapsed;
    if (this.count > 0) {
      gl.clear(gl.COLORBUFFERBIT);
      gl.drawArrays(gl.POINTS, 0, this.count);
    }
    this.onUpdate(delta);
    requestAnimationFrame(this.update);
  }
}
// eslint-disable-next-line no-unused-vars
const pointSize = 2.5;
export default {
  name: "BgAnimation",
  data() {
    return {};
  },
  mounted() {
    const pointSize = 2.5;
    // eslint-disable-next-line no-unused-vars
    const waves = new ShaderProgram(document.querySelector(".waves"), {
      texture:
          "",
      uniforms: {
        size: { type: "float", value: pointSize },
        field: { type: "vec3", value: [0, 0, 0] },
        speed: { type: "float", value: 5 }
      },
      vertex: `
    #define M_PI 3.1415926535897932384626433832795
    precision highp float;
    attribute vec4 a_position;
    attribute vec4 a_color;
    uniform float u_time;
    uniform float u_size;
    uniform float u_speed;
    uniform vec3 u_field;
    uniform mat4 u_projection;
    varying vec4 v_color;
    void main() {
      vec3 pos = a_position.xyz;
      pos.y += (
        cos(pos.x / u_field.x * M_PI * 8.0 + u_time * u_speed) +
        sin(pos.z / u_field.z * M_PI * 8.0 + u_time * u_speed)
      ) * u_field.y;
      gl_Position = u_projection * vec4( pos.xyz, a_position.w );
      gl_PointSize = ( u_size / gl_Position.w ) * 100.0;
      v_color = a_color;
    }`,
      fragment: `
    precision highp float;
    uniform sampler2D u_texture;
    varying vec4 v_color;
    void main() {
      gl_FragColor = v_color * texture2D(u_texture, gl_PointCoord);
    }`,
      onResize(w, h, dpi) {
        const position = [],
            color = [];
        const width = 400 * (w / h);
        const depth = 400;
        const height = 3;
        const distance = 5;
        for (let x = 0; x < width; x += distance) {
          for (let z = 0; z < depth; z += distance) {
            position.push(-width / 2 + x, -30, -depth / 2 + z);
            color.push(
                0,
                1 - (x / width) * 1,
                0.5 + (x / width) * 0.5,
                z / depth
            );
          }
        }
        this.uniforms.field = [width, height, depth];
        this.buffers.position = position;
        this.buffers.color = color;
        this.uniforms.size = (h / 400) * pointSize * dpi;
      }
    });
  }
};
</script>
<style scoped>
canvas {
  display: block;
}
.BgAnimation{
  background-color: #8e8e8e;
}
.waves {
  background-color: #8b8b8b;
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}
</style>